// Copyright (c) 2024 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.


#pragma once

#include <cstdint>
#include <cassert>

#include <ioxx_include_cmsis.hpp>
#include <ioxx_irq.hpp>
#include <type_traits>

extern "C" {

extern uint32_t HAL_GetTick();
extern uint32_t SystemCoreClock;

}

namespace scheduler_basic {

    constexpr uint32_t SYS_TICK_CYCLE_N_MS = 1;  // HAL 中断周期固定为1 毫秒


    /**
     * @brief 更新全局变量，必须在使用其他函数前更新一次
     *
     */
    inline void update_systick_counter() {
        extern volatile uint32_t global_variable_ticks_per_micro_second;
        global_variable_ticks_per_micro_second = SystemCoreClock / 1000'000;
    }

    /**
     * @brief 返回Hal 库的毫秒时间戳
     *
     * Hal 库的systick 中断周期必须设为1ms。
     *
     * @return uint32_t
     */
    inline uint32_t clock_ms() {
        return HAL_GetTick();
    }


    /**
     * @brief 返回微秒时间戳
     *
     * Hal 库的systick 中断周期必须设为1ms。
     *
     * @param update    根据系统时钟更新内部变量
     *
     * @return uint32_t
     */
    inline uint32_t clock_us() {
        static volatile uint32_t last_us = 0;
        extern volatile uint32_t global_variable_ticks_per_micro_second;

        uint32_t ms = clock_ms();
        uint32_t v = SysTick->VAL;

        // 默认SysTick 每个周期就是一毫秒
        uint32_t ticks_per_cycle = SysTick->LOAD + 1;

        uint32_t us = ms * 1000;
        us += (ticks_per_cycle - v) / global_variable_ticks_per_micro_second;

        do {
            irqxx::CriticalScope cs;

            auto last_tmp_read_only = last_us;

            // 这部分代码用来避免SysTick-VAL 重置导致计算结果异常
            if (last_tmp_read_only > us && (last_tmp_read_only - us) < (SYS_TICK_CYCLE_N_MS * 1000)) {
                us = last_tmp_read_only;
            }
            else {
                last_us = us;
            }
        } while (0);

        return us;
    }


    inline uint32_t clock_ticks() {
        return SysTick->LOAD - SysTick->VAL;
    }


    /**
     * @brief 与clock_ticks 函数配合，可以在不能用clock_ms 和clock_us 的地方获取时间差
     *
     * 调用之前，必须至少调用一次clock_us
     *
     * @param ticks
     * @return uint32_t
     */
    inline uint32_t ticks_to_us(uint32_t ticks) {
        extern uint32_t global_variable_ticks_per_micro_second;

        return ticks / global_variable_ticks_per_micro_second;
    }


    inline uint32_t us_to_ticks(uint32_t us) {
        extern uint32_t global_variable_ticks_per_micro_second;

        return us * global_variable_ticks_per_micro_second;
    }

}  // namespace scheduler_basic


// systick 头文件公用的头文件片段，必须放在这里include
#include "./systick_counter/systick_counter_common_fragment.hpp"